import { isNil } from '../../common';
import { ActionType } from '../../flows/actions/action';
import { TriggerType } from '../../flows/triggers/trigger';

export enum StepOutputStatus {
  FAILED = 'FAILED',
  PAUSED = 'PAUSED',
  RUNNING = 'RUNNING',
  STOPPED = 'STOPPED',
  SUCCEEDED = 'SUCCEEDED',
  EXECUTION_LIMIT_REACHED = 'EXECUTION_LIMIT_REACHED',
}

type BaseStepOutputParams<T extends ActionType | TriggerType, OUTPUT> = {
  type: T;
  status: StepOutputStatus;
  input: unknown;
  output?: OUTPUT;
  duration?: number;
  errorMessage?: string;
};

export class GenericStepOutput<T extends ActionType | TriggerType, OUTPUT> {
  type: T;
  status: StepOutputStatus;
  input: unknown;
  output?: OUTPUT;
  duration?: number;
  errorMessage?: string;

  constructor(step: BaseStepOutputParams<T, OUTPUT>) {
    this.type = step.type;
    this.status = step.status;
    this.input = step.input;
    this.output = step.output;
    this.duration = step.duration;
    this.errorMessage = step.errorMessage;
  }

  setOutput(output: OUTPUT): GenericStepOutput<T, OUTPUT> {
    return new GenericStepOutput<T, OUTPUT>({
      ...this,
      output,
    });
  }

  setStatus(status: StepOutputStatus): GenericStepOutput<T, OUTPUT> {
    return new GenericStepOutput<T, OUTPUT>({
      ...this,
      status,
    });
  }

  setErrorMessage(errorMessage: unknown): GenericStepOutput<T, OUTPUT> {
    return new GenericStepOutput<T, OUTPUT>({
      ...this,
      errorMessage,
    });
  }

  static create<T extends ActionType | TriggerType, OUTPUT>({
    input,
    type,
    status,
    output,
  }: {
    input: unknown;
    type: T;
    status: StepOutputStatus;
    output?: OUTPUT;
  }): GenericStepOutput<T, OUTPUT> {
    return new GenericStepOutput<T, OUTPUT>({
      input,
      type,
      status,
      output,
    });
  }
}

export type StepOutput =
  | GenericStepOutput<ActionType.LOOP_ON_ITEMS, LoopStepResult>
  | GenericStepOutput<ActionType.BRANCH, BranchStepResult>
  | GenericStepOutput<ActionType.SPLIT, SplitStepResult>
  | GenericStepOutput<
      | Exclude<
          ActionType,
          ActionType.LOOP_ON_ITEMS | ActionType.BRANCH | ActionType.SPLIT
        >
      | TriggerType,
      unknown
    >;

type BranchStepResult = {
  condition: boolean;
};

export class BranchStepOutput extends GenericStepOutput<
  ActionType.BRANCH,
  BranchStepResult
> {
  static init({ input }: { input: unknown }): BranchStepOutput {
    return new BranchStepOutput({
      type: ActionType.BRANCH,
      input,
      status: StepOutputStatus.SUCCEEDED,
    });
  }
}

type SplitStepResult = {
  evaluatedOptions: { optionName: string; conditionsMatched: boolean }[];
  selectedOptionName: string | undefined;
};

export class SplitStepOutput extends GenericStepOutput<
  ActionType.SPLIT,
  SplitStepResult
> {
  static init({ input }: { input: unknown }): SplitStepOutput {
    return new SplitStepOutput({
      type: ActionType.SPLIT,
      input,
      status: StepOutputStatus.SUCCEEDED,
    });
  }
}

export type LoopStepResult = {
  item: unknown;
  index: number;
  iterations: Record<string, StepOutput>[];
};

export class LoopStepOutput extends GenericStepOutput<
  ActionType.LOOP_ON_ITEMS,
  LoopStepResult
> {
  constructor(
    step: BaseStepOutputParams<ActionType.LOOP_ON_ITEMS, LoopStepResult>,
  ) {
    super(step);
    this.output = step.output ?? {
      item: undefined,
      index: 0,
      iterations: [],
    };
  }

  static init({ input }: { input: unknown }): LoopStepOutput {
    return new LoopStepOutput({
      type: ActionType.LOOP_ON_ITEMS,
      input,
      status: StepOutputStatus.SUCCEEDED,
    });
  }

  hasIteration(iteration: number): boolean {
    return !isNil(this.output?.iterations[iteration]);
  }

  setItemAndIndex({
    item,
    index,
  }: {
    item: unknown;
    index: number;
  }): LoopStepOutput {
    return new LoopStepOutput({
      ...this,
      output: {
        item,
        index,
        iterations: this.output?.iterations ?? [],
      },
    });
  }

  addIteration(): LoopStepOutput {
    return new LoopStepOutput({
      ...this,
      output: {
        item: this.output?.item,
        index: this.output?.index,
        iterations: [...(this.output?.iterations ?? []), {}],
      },
    });
  }
}
